package lexer;

import symbols.FSM_State;
import symbols.Morpheme;
import symbols.Position;

import java.io.IOException;

public class Scanner {

    public static final int START = -100;
    private final Reader reader;
    private int current;
    private int next;

    public Scanner(final Reader reader) {
        this.reader = reader;
        this.current = START;
        this.next = START;
    }

    public Morpheme getNextMorphen() throws IOException {
        int fsm_state = FSM_State.START;
        Morpheme.Codes code = Morpheme.Codes.error;
        boolean finiteState = true;
        boolean noTransition = false;
        StringBuilder read = new StringBuilder();
        Position start = reader.getPosition();
        Position end = reader.getPosition();

        do {
            doReadIn(read);
            end = reader.getPosition();
            switch (fsm_state) {
                case FSM_State.START:
                    if (Character.isDigit(this.current)) {
                        finiteState = true;
                        fsm_state = FSM_State.NUMBER;
                        code = Morpheme.Codes.number;
                    } else if (Character.isLetter(this.current)) {
                        finiteState = true;
                        fsm_state = FSM_State.IDENT;
                        code = FSM_State.getCode(read.toString());
                    } else if (this.current == (':')) {
                        finiteState = false;
                        if (this.next == ('=')) {
                            fsm_state = FSM_State.ASSIGN;
                            code = Morpheme.Codes.assign;
                        } else {
                            fsm_state = FSM_State.ERROR;
                            code = Morpheme.Codes.error;
                        }
                    } else if (Character.isWhitespace(this.current)) {
                        finiteState = false;
                        code = Morpheme.Codes.error;
                        read = new StringBuilder();
                    } else {
                        finiteState = true;
                        noTransition = true;
                        switch (this.current) {
                            case ';':
                                code = Morpheme.Codes.semi;
                                break;
                            case '(':
                                code = Morpheme.Codes.openBracket;
                                break;
                            case ')':
                                code = Morpheme.Codes.closeBracket;
                                break;
                            case '*':
                                code = Morpheme.Codes.mult;
                                break;
                            case '/':
                                code = Morpheme.Codes.slash;
                                break;
                            case '+':
                                code = Morpheme.Codes.plus;
                                break;
                            case '-':
                                code = Morpheme.Codes.score;
                                break;
                            case ',':
                                code = Morpheme.Codes.comma;
                                break;
                            case '=':
                                code = Morpheme.Codes.equal;
                                break;
                            case '>':
                                code = Morpheme.Codes.lessThan;
                                break;
                            case -1:
                                code = Morpheme.Codes.eof;
                                break;
                            default:
                                fsm_state = FSM_State.ERROR;
                                break;
                        }
                    }
                    break;
                case FSM_State.NUMBER:
                    if (!Character.isDigit(this.current)) {
                        code = Morpheme.Codes.error;
                    }
                    break;
                case FSM_State.IDENT:
                    if (!Character.isLetterOrDigit(this.current)) {
                        code = Morpheme.Codes.error;
                    } else {
                        code = FSM_State.getCode(read.toString());
                    }
                    break;
                case FSM_State.ASSIGN:
                    if (this.current == '=') {
                        code = Morpheme.Codes.assign;
                        finiteState = true;
                        noTransition = true;
                    } else {
                        code = Morpheme.Codes.error;
                    }
                    break;
                case FSM_State.ERROR:
                    break;
                default:
                    System.err.println("Should not have entered here.");
                    break;

            }
        } while ((!((isFiniteStmt(this.next) || noTransition) && finiteState)) && !eofReached(this.next));

        if (code.equals(Morpheme.Codes.number)) {
            return new Morpheme(code, start, end, Integer.parseInt(read.toString()));
        } else if (code.equals(Morpheme.Codes.ident)) {
            return new Morpheme(code, start, end, read.toString());
        } else if (eofReached(this.next) && ((fsm_state == FSM_State.ERROR) || (fsm_state == FSM_State.START))) {
            return new Morpheme(Morpheme.Codes.eof, start, end);
        } else {
            return new Morpheme(code, start, end);
        }
    }

    private boolean eofReached(final int next) {
        return (next == -1);
    }

    private boolean isFiniteStmt(final int next) {
        return !(Character.isLetterOrDigit(next));
    }

    private void doReadIn(final StringBuilder read) throws IOException {
        if (this.current == START) {
            this.current = reader.nextChar();
            this.next = reader.nextChar();
        } else {
            this.current = this.next;
            this.next = reader.nextChar();
        }
        read.append((char) this.current);
    }
}